//
// Created by martin on 7/6/21.
//

#include "ftpServer.h"
#include "common.h"
#include "ftpcmdaction.h"
#include "strex.h"

#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sendfile.h>  /* linux独有，不可移植，其他平台可能需要重写 */
#include <pthread.h>

#define AUTH ".auth" /* 保存帐号的文件 */

/**
 * 从cmdline(一行命令文本)获取cmd命令及其参数
 */
static void getCmdFromCmdline(session_t *pses)
{
    /* 去掉末尾ASCII格式末尾<CRLF> */
    strex_trimcrlf(pses->cmdline);
    /* 按FTP命令格式以首个<SP>为分隔符，将命令行切分为命令, 参数两部分 */
    strex_split(pses->cmdline, pses->cmd, pses->arg, " ");
    /* 将命令转化为大写，方便后面和命令-响应动作表比较 */
    strex_toupper(pses->cmd);
}

void ftpserver_process(int ctrlconn_sockfd) {
    ssize_t nread;
    session_t *pses;

    /* 创建session */
    pses = session_create(ctrlconn_sockfd);

    send_reply(ctrlconn_sockfd, 220, "Features: p ."); /* 220: 接受新用户服务准备完成 */

    /* 不断读取命令，执行相应的响应动作 */
    while (1) {
        int i;
        int size;

        /* 检查是否关闭会话 */
        if (pses->close) {
            break;
        }

        bzero(pses->cmdline, sizeof(pses->cmdline));
        bzero(pses->cmd, sizeof(pses->cmd));
        bzero(pses->arg, sizeof(pses->arg));

        nread = readline(ctrlconn_sockfd, pses->cmdline, COMMAND_LINE_MAX);
        printf("recv: %s\n", pses->cmdline);

        /* 从控制连接读取一行命令 */
        if (nread < 0) {
            if (nread == -1) { /* 系统调用出错 */
                perror("readline error");
                session_close(pses);
                break;
            } else { /* -2: 没有找到换行符 */
                /* 500: 语法错误，命令不能被识别 */
                send_reply(ctrlconn_sockfd, 500, "Syntax error, command unrecognized.");
                continue;
            }
        } else if (nread == 0) { /* EOF, 对端已经shutdown连接 */
            fprintf(stdout, "peer shutdown connection\n");
            session_close(pses);
            break;
        }

        getCmdFromCmdline(pses);

        /* 在响应动作映射表中顺序查找当前收到的用户命令, 如果成功找到, 执行相应的响应动作 */
        size = getcmdactionsize(); /* 读取响应动作映射表长度 */
        for (i = 0; i < size; ++i) {
            if (strcmp(cmd_action[i].cmd, pses->cmd) == 0) { /* 在映射表中找到用户当前请求命令 */
                if (cmd_action[i].action != NULL) {
                    cmd_action[i].action(pses);
                } else {
                    /* 502: 命令没有实现 */
                    send_reply(ctrlconn_sockfd, 502, "Command not implemented");
                }

                break; /* 因为命令唯一， 无需继续查找映射表 */
            }
        }

        /* 500: 命令没有正确识别， 可能命令有语法错误 */
        if (i == size) {
            send_reply(ctrlconn_sockfd, 500, "Unrecognized command");
        }
    }

    /* 释放session资源 */
    session_destroy(pses);
    pses = NULL;
}

#if 0
/**
 * 接收命令, 并检查是否合法, 而其立即回复状态码
 */
int ftpserver_recv_cmd(int ctrlconn_sockfd, char *cmd, char *arg) {
    int status = 200;
    char buf[MAXSIZE];

    bzero(buf, sizeof(buf));
    memset(cmd, 0, 5); /* cmd 默认大小, 临时措施. 不安全做法*/
    memset(arg, 0, MAXSIZE);

    /* 从控制连接读取命令(含参数) */
    if (-1 == recv_data(ctrlconn_sockfd, buf, MAXSIZE)) {
        return -1;
    }

    /* 解析出命令 + 参数 */
    strncpy(cmd, buf, 4); /* buf[0..3] => cmd[0..4] */
    strcpy(arg, buf + 5); /* buf[5..] => arg[0..MAXSIZE] */

    if (strcmp(cmd, "QUIT") == 0) {
        status = 221; /* 命令: 服务关闭控制连接 */
    } else if (strcmp(cmd, "USER") == 0 || strcmp(cmd, "PASS")
               || strcmp(cmd, "LIST") == 0 || strcmp(cmd, "RETR")
               || strcmp(cmd, "PUSH") == 0) {
        status = 200; /* command OK */
    } else {
        status = 502; /* 命令没有实现 */
    }

    send_response(ctrlconn_sockfd, status); /* 回送状态 */

    return status;
}



int ftpserver_check_user(const char *user, const char *pass) {
    FILE *fp = fopen(AUTH, "r");
    if (NULL == fp) {
        return -1;
    }

    char username[MAXSIZE];
    char password[MAXSIZE];
    char buf[MAXSIZE];
    char *line = NULL;
    size_t len = 0;
    int auth = -1;

    /* 按行从文件读取内容 */
    while (-1 != getline(&line, &len, fp)) {
        bzero(buf, sizeof(buf));
        strcpy(buf, line); /* 复制内容 line => buf */

        char *post = strtok(NULL, " "); /* 分割出用户名(第一个<SP>之前字符串) */
        strcpy(username, post);

        if (NULL != post) {
            char *passpost = strtok(NULL, " "); /* 分割出密码(第二个<SP>之前的字符串) */
            strcpy(password, post);
        }

        trimstr(password, (int) strlen(password)); /* 去掉字符串中的空白字符, 替换为'\0' */

        /* 检查登录用户名和密码 */
        if (strcmp(user, username) == 0 && strcmp(pass, password) == 0) {
            auth = 1;
            break;
        }
    }

    free(line);
    fclose(fp);

    return auth;
}

int ftpserver_start_data_conn(int ctrlconn_sockfd) {
    int wait;
    if (recv(ctrlconn_sockfd, &wait, sizeof(wait), 0) < 0) {
        return -1;
    }

    char buf[1024];
    struct sockaddr_in client_addr;
    socklen_t len = sizeof(client_addr);

    /* 获取对端(用户) IP地址信息, 并转化为文本串, 存放到buf缓存 */
    getpeername(ctrlconn_sockfd, (SA *) &client_addr, &len);
    inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf));

    /* 根据对端IP地址信息, 创建数据连接, 服务端使用指定端口号CLIENT_PORT */
    int sock_data = socket_connect(buf, CLIENT_PORT);
    if (sock_data < 0) {
        return -1;
    }

    return sock_data;
}


int ftpserver_list(int sock_data, int ctrlconn_sockfd) {
    /* 执行shell命令， 列出当前目下ftp目录的所有子文件，并将结果覆盖存入temp.txt文件 */
    int ret = system("ls -l ftp > temp.txt");
    if (ret < 0) {
        return -1;
    }

    int fd = open("temp.txt", O_RDONLY);
    send_response(ctrlconn_sockfd, 1);  /* 准备发送数据 */

    struct stat st;
    stat("temp.txt", &st);
    size_t size = st.st_size; /* 文件长度 */

    sendfile(sock_data, fd, NULL, size);

    close(fd);

    send_response(ctrlconn_sockfd, 226); /* 226: 请求文件动作成功, 关闭数据连接 */
    return 0;
}


/**
 * RETR命令处理函数
 */
int ftpserver_retr(int sock_data, int ctrlconn_sockfd, char *filename) {
    char name[260];
    bzero(name, sizeof(name));

    /* 文件都位于ftp目录 */
    strcpy(name, "ftp/");
    strcat(name, filename);

    int fd = open(name, O_RDONLY);
    if (fd < 0) {
        send_response(ctrlconn_sockfd, 550); /* 550: 请求的动作没有执行, 文件不可用*/
        return -1;
    } else {
        send_response(ctrlconn_sockfd, 150); /* 150: 文件状态OK，将打开数据连接 */

        struct stat st;
        stat(name, &st);
        size_t size = st.st_size;

        sendfile(sock_data, fd, NULL, size); /* 发送文件 */
        send_response(ctrlconn_sockfd, 226); /* 226: 请求文件动作成功, 关闭数据连接 */
        close(fd);
    }

    return 0;
}

void ftpserver_push(int sock_data, int ctrlconn_sockfd, char *filename) {
    int ack;
    if (recv(ctrlconn_sockfd, &ack, sizeof(ack), 0) < 0) {
        send_response(ctrlconn_sockfd, 502); /* 502: 命令没有实现 ??? 这里实际上是接收错误 */
    }

    int status = ntohl(ack); /* ack时从客户端读取的, 用户回命令ack??? */
    if (553 == status) {
        send_response(ctrlconn_sockfd, 553); /* 553: 请求动作未获得. 文件名不允许 */
        return;
    }

    /*
     * 根据文件名设置文件路径 (添加所在路径: "ftp/"前缀)
     * name = ftp/{filename}
     */
    char name[260];
    bzero(name, sizeof(name));
    strcpy(name, "ftp/");
    strcat(name, filename);

    int fd = open(name, O_CREAT | O_WRONLY, 0664);
    if (fd < 0) {
        send_response(ctrlconn_sockfd, 502); /* 502: 命令没有实现 ??? 这里分明是打开文件出错 */
        return;
    }

    while (1) {
        char data[MAXSIZE];
        bzero(data, sizeof(data));
        ssize_t n = recv(sock_data, data, sizeof(data), 0);
        if (n < 0) {
            if (n < 0) {
                send_response(ctrlconn_sockfd, 502); /* 502: 命令没有实现 ??? 这里分明是打开文件出错 */
            } else { /* n == 0 说明对端在shutdown连接, 即文件传输完毕 */
                send_response(ctrlconn_sockfd, 226); /* 226: 关闭数据连接，请求文件成功 */
            }
            break;
        }

        write(fd, data, n); /* 将接收到的data写入fd指向的文件 */
    }

    /*  */
    close(fd);
}

#endif